package com.step.mesh;

import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import javax.microedition.khronos.opengles.GL10;

import com.step.Step.Dimension;
import com.step.mesh.buffer.ColorBuffer;
import com.step.mesh.buffer.CoordinateBuffer;
import com.step.mesh.buffer.FaceBuffer;
import com.step.sprite.Sprite.RenderType;

public class Mesh {	
	CoordinateBuffer positionBuffer;
	ColorBuffer colorBuffer;
	CoordinateBuffer uvBuffer;
	CoordinateBuffer normalBuffer;
	FaceBuffer faceBuffer;
	final Dimension dimension;
	final boolean hasColor;
	final boolean hasUv;
	final boolean hasNormal;
	final int cooNum;
	boolean hasFace;
	protected String name;

	/**
	 * 创建网格，允许使用颜色、纹理；如果是3ｄ的，还允许使用法线
	 * @param dimension
	 * @param vertexNum
	 */
	public Mesh(Dimension dimension, int vertexNum) {
		this(dimension, vertexNum, vertexNum / 2, true, true, dimension == Dimension.d3d);		
	}
	
	public Mesh(Dimension dimension, int vertexNum, int faceNum){
		this(dimension, vertexNum, faceNum, true, true, dimension == Dimension.d3d);
	}
	/**
	 * 创建网格对象
	 * @param dimension 维度
	 * @param vertexNum 顶点数
	 * @param useColor 是否使用颜色
	 * @param useUv 是否使用纹理
	 * @param useNormal 是否使用法线
	 * @param useFace 是否使用索引面
	 */
	public Mesh(Dimension dimension, int vertexNum, int faceNum, boolean useColor, boolean useUv, boolean useNormal){
		this.dimension = dimension;
		
		this.hasColor = useColor;
		this.hasUv = useUv;		
		
		if(dimension == Dimension.d2d){
			cooNum = 2;
			this.hasNormal = false;
		}
		else{
			cooNum = 3;
			this.hasNormal = useNormal;
		}
		
		positionBuffer = new CoordinateBuffer(vertexNum, cooNum);
		faceBuffer = new FaceBuffer(faceNum);
		if(hasColor) colorBuffer = new ColorBuffer(vertexNum);
		if(hasUv) uvBuffer = new CoordinateBuffer(vertexNum, 2);
		if(hasNormal) normalBuffer = new CoordinateBuffer(vertexNum, cooNum);
	}
	
	public Mesh(Dimension dimension, CoordinateBuffer positions, ColorBuffer colors, CoordinateBuffer uvs, CoordinateBuffer normals){
		this.dimension = dimension;
		this.cooNum = dimension == Dimension.d2d ? 2 : 3;
		this.positionBuffer = positions;
		this.colorBuffer = colors;
		this.uvBuffer = uvs;
		this.normalBuffer = normals;
		
		this.hasColor = colors != null && colors.size() > 0;
		this.hasNormal = dimension == Dimension.d3d && normals != null && normals.size() > 0;
		this.hasUv = uvs != null && uvs.size() > 0;
		this.hasFace = false;
	}
	/**
	 * 添加3D顶点信息
	 * @param x 位置x坐标
	 * @param y 位置y坐标
	 * @param z 位置z坐标
	 * @param r 红色分量
	 * @param g 绿色分量
	 * @param b 蓝色分量
	 * @param a 透明值分量
	 * @param u 纹理u坐标
	 * @param v 纹理v坐标
	 * @param nx 法线x坐标
	 * @param ny 法线y坐标
	 * @param nz 法线z坐标
	 */
	public void addVertex(
			float x, 
			float y, 
			float z,
			int r,
			int g,
			int b,
			int a,
			float u,
			float v,
			float nx,
			float ny,
			float nz){
		if(dimension == Dimension.d2d) throw new RuntimeException("Mesh:2D网格不允许添加3D信息的顶点");
		positionBuffer.add(x, y, z);
		
		if(hasColor) colorBuffer.add(r, g, b, a);
		if(hasUv) uvBuffer.add(u, v);
		if(hasNormal) normalBuffer.add(nx, ny, nz);
	}
	
	/**
	 * 添加3D顶点信息
	 * @param x 位置x坐标
	 * @param y 位置y坐标
	 * @param z 位置z坐标
	 * @param r 红色分量
	 * @param g 绿色分量
	 * @param b 蓝色分量
	 * @param a 透明值分量
	 * @param u 纹理u坐标
	 * @param v 纹理v坐标
	 */
	public void addVertex(
			float x, 
			float y, 
			float z,
			int r,
			int g,
			int b,
			int a,
			float u,
			float v){
		if(dimension == Dimension.d2d) throw new RuntimeException("Mesh:2D网格不允许添加3D信息的顶点");
		positionBuffer.add(x, y, z);
		
		if(hasColor) colorBuffer.add(r, g, b, a);
		if(hasUv) uvBuffer.add(u, v);
	}
	
	/**
	 * 添加3D顶点信息
	 * @param x 位置x坐标
	 * @param y 位置y坐标
	 * @param z 位置z坐标
	 * @param r 红色分量
	 * @param g 绿色分量
	 * @param b 蓝色分量
	 * @param a 透明值分量
	 */
	public void addVertex(
			float x, 
			float y, 
			float z,
			int r,
			int g,
			int b,
			int a){
		if(dimension == Dimension.d2d) throw new RuntimeException("Mesh:2D网格不允许添加3D信息的顶点");
		positionBuffer.add(x, y, z);
		
		if(hasColor) colorBuffer.add(r, g, b, a);
	}
	
	/**
	 * 添加3D顶点信息
	 * @param x 位置x坐标
	 * @param y 位置y坐标
	 * @param z 位置z坐标
	 * @param r 红色分量
	 * @param g 绿色分量
	 * @param b 蓝色分量
	 * @param a 透明值分量
	 * @param u 纹理u坐标
	 * @param v 纹理v坐标
	 * @param nx 法线x坐标
	 * @param ny 法线y坐标
	 * @param nz 法线z坐标
	 */
	public void addVertex(
			float x, 
			float y, 
			float z,
			int r,
			int g,
			int b,
			int a,
			float nx,
			float ny,
			float nz){
		if(dimension == Dimension.d2d) throw new RuntimeException("Mesh:2D网格不允许添加3D信息的顶点");
		positionBuffer.add(x, y, z);
		
		if(hasColor) colorBuffer.add(r, g, b, a);
		if(hasNormal) normalBuffer.add(nx, ny, nz);
	}
	
	/**
	 * 添加3D顶点信息
	 * @param x 位置x坐标
	 * @param y 位置y坐标
	 * @param z 位置z坐标
	 * @param u 纹理u坐标
	 * @param v 纹理v坐标
	 */
	public void addVertex(
			float x, 
			float y, 
			float z,
			float u,
			float v){
		if(dimension == Dimension.d2d) throw new RuntimeException("Mesh:2D网格不允许添加3D信息的顶点");
		positionBuffer.add(x, y, z);
		if(hasUv) uvBuffer.add(u, v);
	}
	
	/**
	 * 添加2D顶点信息
	 * @param x 位置x坐标
	 * @param y 位置y坐标
	 * @param r 红色分量
	 * @param g 绿色分量
	 * @param b 蓝色分量
	 * @param a 透明值分量
	 * @param u 纹理u坐标
	 * @param v 纹理v坐标
	 */
	public void addVertex(
			float x, 
			float y, 
			int r,
			int g,
			int b,
			int a,
			float u,
			float v){
		if(dimension == Dimension.d3d) throw new RuntimeException("Mesh:3D网格不允许添加2D信息的顶点");
		positionBuffer.add(x, y);
		
		if(hasColor) colorBuffer.add(r, g, b, a);
		if(hasUv) uvBuffer.add(u, v);
	}
	
	/**
	 * 添加2D顶点信息
	 * @param x 位置x坐标
	 * @param y 位置y坐标
	 * @param r 红色分量
	 * @param g 绿色分量
	 * @param b 蓝色分量
	 * @param a 透明值分量
	 */
	public void addVertex(
			float x, 
			float y, 
			int r,
			int g,
			int b,
			int a){
		if(dimension == Dimension.d3d) throw new RuntimeException("Mesh:3D网格不允许添加2D信息的顶点");
		positionBuffer.add(x, y);
		
		if(hasColor) colorBuffer.add(r, g, b, a);
	}
	
	/**
	 * 添加2D顶点信息
	 * @param x 位置x坐标
	 * @param y 位置y坐标
	 * @param u 纹理u坐标
	 * @param v 纹理v坐标
	 */
	public void addVertex(
			float x, 
			float y, 
			float u,
			float v){
		if(dimension == Dimension.d3d) throw new RuntimeException("Mesh:3D网格不允许添加2D信息的顶点");
		positionBuffer.add(x, y);
		
		if(hasUv) uvBuffer.add(u, v);
	}
	
	/**
	 * 添加已定义好的顶点信息
	 * @param vertex
	 */
	public void addVertex(Vertex vertex){
		if(dimension == Dimension.d2d && vertex.position.is3D()){
			throw new RuntimeException("Mesh:2D网格不允许添加3D信息的顶点");
		}
		else if(dimension == Dimension.d3d && vertex.position.is2D()){
			throw new RuntimeException("Mesh:3D网格不允许添加2D信息的顶点");
		}
		
		positionBuffer.add(vertex.position);
		if(hasColor && vertex.hasColor()) colorBuffer.add(vertex.color);
		if(hasUv && vertex.hasUV()) uvBuffer.add(vertex.uv);
		if(hasNormal && vertex.hasNormal()) normalBuffer.add(vertex.normal);
	}
	
	/**
	 * 添加顶点信息
	 * @param position 位置对象
	 * @param color 颜色对象
	 * @param uv 纹理对象
	 * @param normal 法线对象
	 */
	public void addVertex(Coordinate position, Color color, Coordinate uv, Coordinate normal){
		if(dimension == Dimension.d2d && position.is3D()){
			throw new RuntimeException("Mesh:2D网格不允许添加3D信息的顶点");
		}
		else if(dimension == Dimension.d3d && position.is2D()){
			throw new RuntimeException("Mesh:3D网格不允许添加2D信息的顶点");
		}
		
		if(uv.numbers() != 2){
			throw new RuntimeException("Mesh:纹理坐标应该只有两个坐标值(u,v)");
		}
		
		if(position != null) positionBuffer.add(position);
		if(hasColor && color != null) colorBuffer.add(color);
		if(hasUv && uv != null) uvBuffer.add(uv);
		if(hasNormal && dimension == Dimension.d3d && normal != null) normalBuffer.add(normal);
	}
	
	/**
	 * 添加顶点信息
	 * @param position 位置对象
	 * @param color 颜色对象
	 * @param uv 纹理对象
	 */
	public void addVertex(Coordinate position, Color color, Coordinate uv){
		addVertex(position, color, uv, null);
	}
	
	/**
	 * 添加顶点信息
	 * @param position 位置对象
	 * @param color 颜色对象
	 */
	public void addVertex(Coordinate position, Color color){
		addVertex(position, color, null, null);
	}
	
	/**
	 * 添加顶点信息
	 * @param position 位置对象
	 * @param uv 纹理对象
	 */
	public void addVertex(Coordinate position, Coordinate uv){
		addVertex(position, null, uv, null);
	}
	
	/**
	 * 添加索引面
	 * @param a 顶点序号1
	 * @param b 顶点序号2
	 * @param c 顶点序号3
	 */
	public void addFace(int a, int b, int c){
		if(hasFace == false) hasFace = true;		
		if(faceBuffer == null) faceBuffer = new FaceBuffer(positionBuffer.size() * 2);
		
		faceBuffer.add(a, b, c);
	}
	
	/**
	 * 添加索引面
	 * @param face
	 */
	public void addFace(Face face){
		addFace(face.a, face.b, face.c);
	}
	
	/**
	 * 添加所有索引面
	 * @param faces
	 */
	public void addAllFaces(Face...faces){
		if(hasFace == false) hasFace = true;
		
		if(faceBuffer == null) {
			faceBuffer = new FaceBuffer(positionBuffer.size() * 2);
		}
		else{
			faceBuffer.clear();
		}
		
		int len = faces.length;
		
		for (int i = 0; i < len; i++) {
			faceBuffer.add(faces[i]);
		}
	}
	
	public void render(GL10 gl, RenderType renderType){
		drawNormal(gl);
		drawColor(gl);
		drawTexCoordinate(gl);
		drawPosition(gl, renderType.glValue());
	}
	
	// 绘制颜色
	private void drawColor(GL10 gl){
		if(hasColor){
			gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
			ByteBuffer color = colorBuffer.buffer();
			color.position(0);
			gl.glColorPointer(4, GL10.GL_UNSIGNED_BYTE, 0, color);	
		}
		else{
			gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
		}
	}
	
	// 绘制纹理坐标
	private void drawTexCoordinate(GL10 gl){
		if(hasUv){
			FloatBuffer floatBuffer = uvBuffer.buffer();
			floatBuffer.position(0);
			gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, floatBuffer);
			gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		}
		else{
			gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		}
	}
	// 绘制法线
	private void drawNormal(GL10 gl) {
		if(hasNormal){
			FloatBuffer normals = normalBuffer.buffer();
			normals.position(0);
			gl.glNormalPointer(GL10.GL_FLOAT, 0, normals);
			gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
		}
		else{
			gl.glDisableClientState(GL10.GL_NORMAL_ARRAY);
		}
	}
	
	// 绘制顶点位置
	private void drawPosition(GL10 gl, int renderType){
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		FloatBuffer positions = positionBuffer.buffer();
		positions.position(0);
		gl.glVertexPointer(cooNum, GL10.GL_FLOAT, 0, positions);
		
		if(hasFace){
			ShortBuffer faces = faceBuffer.buffer();
			faces.position(0);
			gl.glDrawElements(renderType, faceBuffer.size() * FaceBuffer.PROPERTIES_PER_ELEMENT,	GL10.GL_UNSIGNED_SHORT, faces);
		}
		else{
			gl.glDrawArrays(renderType, 0, positionBuffer.size());
		}
	}
	
	public void dispose(){
		if(positionBuffer != null) positionBuffer.clear();
		if(colorBuffer != null) colorBuffer.clear();
		if(uvBuffer != null) uvBuffer.clear();
		if(normalBuffer != null) normalBuffer.clear();
		if(faceBuffer != null) faceBuffer.clear();
	}
	
	public Mesh clone(){
		return new Mesh(dimension, positionBuffer.clone(), colorBuffer.clone(), uvBuffer.clone(), normalBuffer.clone());
	}

	public Dimension getDimension() {
		return dimension;
	}

	public boolean hasColor() {
		return hasColor;
	}

	public boolean hasUv() {
		return hasUv;
	}

	public boolean hasNormal() {
		return hasNormal;
	}

	public boolean is2D() {
		return dimension == Dimension.d2d;
	}

	public boolean is3D(){
		return dimension == Dimension.d3d;
	}
	
	public boolean hasFace() {
		return hasFace;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	public String getName(){
		return name;
	}
	
	public CoordinateBuffer getUvBuffer(){
		return this.uvBuffer;
	}
	
	public CoordinateBuffer getPositionBuffer(){
		return this.positionBuffer;
	}
	
	public CoordinateBuffer getNormalBuffer(){
		return this.normalBuffer;
	}
}
